
#include <stdio.h>
#include <Socket.h>
#include <exl_timer.h>


namespace exl {
namespace net {

//------------------------------------------------------------------

bool SocketBase::started = false;

void SocketBase::init()
{
	if (started) return;

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (!err) started = true;
}

SocketBase::SocketBase(Protocol prot) : type(prot)
{
	SocketBase::init();
}


//------------------------------------------------------------------

TcpSocket::TcpSocket() : SocketBase(TCP)
{
	socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	//else if (type == UDP) socket = ::socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
}

TcpSocket::~TcpSocket()
{
	close();
}


bool TcpSocket::canRead(int timeout)
{
	if (timeout < 0) return false;

	fd_set rfds = {1, {socket}};
	timeval tm = {0, timeout * 1000};
	int result = select(0, &rfds, 0, 0, &tm);

	switch (result)
	{
	case 1: return true;
	case 0: return false;	// nao ha dados de entrada
	case SOCKET_ERROR:
		this->close();
		return false;
	default:
		return false;
	}
}


int TcpSocket::send(void * buffer, int size)
{
	int res = 0, bytes_sent = 0;
    for(int i = 0; i <= size; i += packetsize)
	{
		res = ::send(socket, (char*)buffer + i, (packetsize<(size-i)?packetsize:(size-i)), 0);
		if (res <= 0)
		{
			close();
			return 0;
        }
		bytes_sent += res;
    }
	return bytes_sent;
}

/**
 Parametros:
	@param buffer	buffer para armazenar dados
	@param size		tamanho do buffer
	@param timeout	tempo em milisegundos para aguardar por dados
					passado este tempo retorna 0
					se for especificado Blocking para este parametro
					o metodo eh bloqueante

	@return			numero de bytes lidos
 */
int TcpSocket::recv(void * buffer, int size, int timeout)
{
	int bytes_recv = 0;

	if (timeout < 0)		// bloqueante
	{
		bytes_recv = ::recv(socket, (char *) buffer, size, 0);
		if (bytes_recv <= 0)	// erro
		{
			bytes_recv = 0;
			this->close();
		}
	}
	else					// nao bloqueante
	{
		exl::timer::Chronometer chrono;

		while ((int)chrono.getTimeMilliseconds() < timeout)
		{
			chrono.start();
			if (canRead(max(timeout - chrono.getTimeMilliseconds(), 0)))
			{
				int result = ::recv(socket, (char *) buffer + bytes_recv, size - bytes_recv, 0);
				if (result <= 0)	//erro
				{
					close();
					break;
				}
				else bytes_recv += result;
			}
			else break;
			chrono.stop();
		}
	}

	return bytes_recv;
}

bool TcpSocket::connected()
{
	fd_set wfds = {1, {socket}};
	fd_set efds = {1, {socket}};
	timeval tm = {0, 0};

	int result = select(0, 0, &wfds, &efds, &tm);
	if (result == 1 && FD_ISSET(socket, &wfds) && !FD_ISSET(socket, &efds))
	{
		if (canRead(0))
		{
			char buffer[64];
			int result = ::recv(socket, (char *) buffer, 64, MSG_PEEK);
			if (result < 0) return false;
			else return true;
		}
		else return true;
	}
	else return false;
}

void TcpSocket::close()
{
	closesocket(socket);
}

void TcpSocket::getRemoteIp(int ip[4])
{
	ip[0] = remote.sin_addr.S_un.S_un_b.s_b1;
	ip[1] = remote.sin_addr.S_un.S_un_b.s_b2;
	ip[2] = remote.sin_addr.S_un.S_un_b.s_b3;
	ip[3] = remote.sin_addr.S_un.S_un_b.s_b4;
}

int TcpSocket::getLocalPort()
{
	return ntohs(local.sin_port);
}

int TcpSocket::getRemotePort()
{
	return ntohs(remote.sin_port);
}


bool TcpSocket::connect(const IpAddress & ip, int port)
{
	remote.sin_family = AF_INET;
	remote.sin_port = htons(port);
	remote.sin_addr.S_un.S_un_b.s_b1 = ip.b[0];
	remote.sin_addr.S_un.S_un_b.s_b2 = ip.b[1];
	remote.sin_addr.S_un.S_un_b.s_b3 = ip.b[2];
	remote.sin_addr.S_un.S_un_b.s_b4 = ip.b[3];

	memset(remote.sin_zero, 0, sizeof(remote.sin_zero));

	int result = ::connect(socket, (struct sockaddr *) &remote, sizeof(remote));
	if (result) return false;

	int loc_addrlen = sizeof(local);
	int rmt_addrlen = sizeof(remote);
	getsockname(socket, (struct sockaddr *) &local, &loc_addrlen);
	getpeername(socket, (struct sockaddr *) &remote, &rmt_addrlen);

	return true;
}



/******************************************
 * TcpServerSocket class
 ******************************************/
TcpServerSocket::TcpServerSocket(int port) : SocketBase(TCP), listening(false)
{
	socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.s_addr = INADDR_ANY;
	memset(server.sin_zero, 0, sizeof(server.sin_zero));

	if (bind(socket, (struct sockaddr *) &server, sizeof(server)) != 0)
	{
		printf("bind\n");
		closesocket(socket);
		return;
	}

	if (listen(socket, 5) != 0)
	{
		printf("listen\n");
		closesocket(socket);
		return;
	}

	listening = true;
}

TcpServerSocket::~TcpServerSocket() {}

bool TcpServerSocket::accept(TcpSocket * sock, int timeout)
{
	if (sock->type != this->type) return false;

	int rmt_addrlen = sizeof(sock->remote);

	// Caso funcao nao deva ser bloqueante verifica se o socket
	// esta pronto para aceitar uma conexao
	int result = 1;
	if (timeout >= 0)
	{
		// Verifica se funcao deve ser bloqueante ou nao
		fd_set rfds = {1, {this->socket}};
		timeval tm = {0, timeout * 1000};
		result = select(0, &rfds, 0, 0, &tm);
	}

	if (result == 0) return false;
	else if (result == SOCKET_ERROR)
	{
		printf("select\n");
		listening = false;
		closesocket(socket);
		return false;
	}
	else if (result != 1)
	{
		return false;
	}

	SOCKET s = ::accept(this->socket, (struct sockaddr *) &sock->remote, &rmt_addrlen);
	if (s == INVALID_SOCKET)
	{
		printf("accept\n");
		listening = false;
		return false;
	}

	// Initialize TcpSocket object
	sock->socket = s;

	int loc_addrlen = sizeof(sock->local);
	getsockname(s, (struct sockaddr *) &sock->local, &loc_addrlen);
	getpeername(s, (struct sockaddr *) &sock->remote, &rmt_addrlen);

	return true;
}

} }		// namespace exl::net
